<?php
class CDbConnectionManager extends CApplicationComponent {
	/**
	 * 数据连接
	 * @var Array CDbConnection
	 */
	private static $_connections = array();
	
	/**
	 * 主库键 => 从库HASH 的映射关系
	 */
	private static $_slaves = array();
	
	/**
	 * 当前的数据库连接配置
	 * @var Array
	 */
	private $_config = array();
	
	/**
	 * 分库键值
	 * 根据该值确定所要连接的数据库
	 * @var String
	 */
	private $_dbKey = '';
	
	/**
	 * 分表键值
	 * 根据该值确定表名称
	 * @var String
	 */
	private $_tableKey = '';
	
	/**
	 * 所有的数据库连接配置
	 * @var Array
	 */
	public $connectionConfig = array();
	
	/**
	 * 分库方式
	 * 对应 CShardedMethod 类中的 方法名
	 * @var String 
	 */
	public $dbShardedMethod = '';
	
	/**
	 * 分表方式
	 * 对应 CShardedMethod 类中的 方法名
	 * @var String 
	 */
	public $tableShardedMethod = '';
	
	/**
	 * 通用的数据库连接用户
	 * @var String
	 */
	public $username = '';
	
	/**
	 * 通用的数据库连接密码
	 * @var String
	 */
	public $password = '';
	
	/**
	 * 通用的数据字符集
	 * @var String
	 */
	public $charset = 'utf8';
	
	/**
	 * 是否启用从库连接
	 * @var bool $enableSlave 
	 */
	public $enableSlave = true;
	
	/**
	 * PDO::ATTR_TIMEOUT
	 */
	public $pdoTimeout = 2;
	
	/**
	 * 数据库会话最大等待时间，超过此时间未活动则该数据库连接超时
	 */
	public $waitSession = 15;
	
	/**
	 * 获取数据库连接
	 *
	 * @param Boolean $isMaster 是否为主库链接，默认为 true
	 * @return CDbConnection
	 */
	public function getDbConnection($isMaster = true) {
		return $isMaster ? $this->getMasterDbConnection() : $this->getSlaveDbConnection();
	}
	
	/**
	 * 获取主库连接
	 * 
	 * @return CDbConnection
	 */
	protected function getMasterDbConnection() {
		return $this->createDbConnection($this->_config, true);
	}
	
	/**
	 * 获取从库连接
	 * 
	 * @return CDbConnection
	 */
	protected function getSlaveDbConnection() {
		if(!array_key_exists($this->_dbKey, self::$_slaves)) {
			$slavesConfig = $this->_config['slaves'];
			shuffle($slavesConfig['connectionStrings']);
			$slavesConfig['connectionString'] = $slavesConfig['connectionStrings'][0];
			
			$this->createDbConnection($slavesConfig, false);
		}
		
		$hash = self::$_slaves[$this->_dbKey];
		return self::$_connections[$hash];
	}
	
	/**
	 * 设置分库分表键值
	 */
	public function sharded($value) {
		$shardedModel = CShardedMethod::model();
		
		if(!$shardedModel->exists($this->dbShardedMethod)) throw new CDbException('DB sharded method is not exists!');
		if(!$shardedModel->exists($this->tableShardedMethod)) throw new CDbException('Table sharded method is not exists!');
		
		$this->_dbKey = $shardedModel->{$this->dbShardedMethod}($value);
		
		if(!array_key_exists($this->_dbKey, $this->connectionConfig)) throw new CDbException('Invalid sharded method or value!');
		
		$this->_config = $this->connectionConfig[$this->_dbKey];
		$this->_tableKey = $shardedModel->{$this->tableShardedMethod}($value);
		
		Yii::trace(sprintf('dbKey: %s, tableKey: %s', $this->_dbKey, $this->_tableKey), __METHOD__.':'.__LINE__);
		return $this;
	}
	
	/**
	 * 获取分表键值
	 */
	public function getShardedTableKey() {
		return $this->_tableKey;
	}
	
	/**
	 * 获取分库键值
	 */
	public function getShardedDbKey() {
		return $this->_dbKey;
	}
	
	/**
	 * @return dbConnection->createCommand
	 */
	public function createCommand($sql = null) {
		return $this->dbConnection->createCommand($sql);
	}
	
	/**
	 * 通过数据库配置创建数据库连接
	 * 
	 * @param Array $config
	 * @param Boolean $isMaster 是否为创建主库链接
	 * @return CDbConnection
	 */
	protected function createDbConnection($config, $isMaster = true) {
		$key = md5($config['connectionString']);
		if(!array_key_exists($key, self::$_connections)) {
			!isset($config['charset']) && $config['charset'] = $this->charset;
			!isset($config['username']) && $config['username'] = $this->username;
			!isset($config['password']) && $config['password'] = $this->password;
			
			$config['connectionString'] .= sprintf('charset=%s;', $config['charset']);
			
			Yii::trace('Create '.($isMaster ? 'master' : 'slave').' DB connection: '.$config['connectionString'], __METHOD__.':'.__LINE__);
			
			$connection = new CDbConnectionMasterSlave($config['connectionString'], $config['username'], $config['password']);
			$connection->pdoTimeout = $this->pdoTimeout;
			$connection->waitSession = $this->waitSession;
			$connection->enableSlave = $isMaster ? $this->enableSlave : false;
			
			self::$_connections[$key] = $connection;
		}
		
		!$isMaster && self::$_slaves[$this->_dbKey] = $key;
		return self::$_connections[$key];
	}
}